package cn.itcast.nio.c4;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;

/**
 * @author Dee
 * @date 2023/10/19
 * <p>Description: NIO demo, 使用Selector管理, 动态扩容
 */
@Slf4j
public class DeeServer {
    public static void main(String[] args) throws IOException {
        // 开启服务器
        ServerSocketChannel ssc = ServerSocketChannel.open();
        // 设置为非阻塞
        ssc.configureBlocking(false);
        // 绑定端口
        ssc.bind(new InetSocketAddress(8080));
        // 创建select管理多个channel
        Selector sel = Selector.open();
        // 注册ssc
        SelectionKey sscKey = ssc.register(sel, 0, null);
        log.info("注册ssc, {}", sscKey);
        // 关注接收事件
        sscKey.interestOps(SelectionKey.OP_ACCEPT);
        while (true) {
            sel.select(); // 无事件阻塞, 有事件继续执行
            log.info("有事件接入");
            // 遍历selKeys
            Iterator<SelectionKey> ite = sel.selectedKeys().iterator();
            while (ite.hasNext()) {
                SelectionKey sk = ite.next();
                log.info("迭代sk, {}", sk);
                ite.remove(); // 处理key的时候一定用删除, 否则每次循环处理相同key会npe
                if (sk.isAcceptable()) { // 如果是访问事件
                    // 获取通道
                    SelectableChannel channel = sk.channel();
                    // 强转为 ssc
                    ServerSocketChannel ssch = (ServerSocketChannel) channel;
                    // 建立连接
                    SocketChannel sc = ssch.accept();
                    sc.configureBlocking(false); // 设置为非阻塞
                    // 分配空间
                    ByteBuffer buffer = ByteBuffer.allocate(16); // attachment
                    // 注册sc, 将buffer作为附件关联
                    SelectionKey scKey = sc.register(sel, 0, buffer);
                    // 关注读事件
                    scKey.interestOps(SelectionKey.OP_READ);
                    log.info("连接成功, {}", sc);
                } else if (sk.isReadable()) { // 如果是读取事件
                    try {
                        // 获取通道并强转
                        SocketChannel channel = (SocketChannel) sk.channel();
                        // 获取附件(buffer)
                        ByteBuffer buffer = (ByteBuffer) sk.attachment();
                        int read = channel.read(buffer); // 如果是正常断开, read是-1
                        if (read == -1) {
                            sk.cancel();
                        } else {
                            // 截取\n
                            split(buffer);
                            // 如果截取后发现指针没变, 说明没接到\n, 需要扩容
                            if (buffer.position() == buffer.limit()) {
                                // 创建新的bf并替换attachment
                                ByteBuffer newBuffer = ByteBuffer.allocate(buffer.capacity() << 1);
                                buffer.flip();
                                newBuffer.put(buffer);
                                sk.attach(newBuffer);
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        sk.cancel(); // 事件取消注册, 因为客户端已经断开, 会产生一个读事件, 因此不处理会死循环
                    }
                }


            }
        }
    }

    /**
     *
     * @param source
     */
    private static void split(ByteBuffer source) {
        source.flip();
        // 循环遍历
        for (int i = 0; i < source.limit(); i++) {
            if (source.get(i) == '\n') {
                int l = i - source.position();
                ByteBuffer buffer = ByteBuffer.allocate(l);
                for (int j = 0; j < l; j++) {
                    buffer.put(source.get());
                }
                source.get();
                buffer.flip(); // 此处别忘了, 转换为读模式
                System.out.println(StandardCharsets.UTF_8.decode(buffer));
            }
        }
        source.compact();
    }
}
